import java.math.BigDecimal
import java.util.ArrayList
import java.util.HashMap
import java.util.Stack
import java.util.regex.Matcher
import java.util.regex.Pattern

/**
 * Created by whizzmirray on 4/01/17. Parser Class uses the reverse polish
 * expression to parse arithmetical and mathematical equations
 */
object Parser {

    private val tokenPattern = Pattern
            .compile("\\s*([0-9]*\\.?[0-9]+E(\\+|-)?[0-9]+|[A-Za-z0-9_]+|(\\+|-)?[0-9]*\\.?[0-9]+|\\S)\\s*")
    private val operanPattern = Pattern.compile("^(\\+|-)?[0-9]*\\.?[0-9]+|[A-Za-z]|π")// [0-9]+E(\\+{0,1}|-)[0-9]+
    private val namePattern = Pattern.compile("^[A-Za-z_]$")//
    private val opPattern = Pattern.compile("^\\+|-|\\*|/|\\(|\\)|\\^|\\$|%|!$")
    private val funcPattern = Pattern.compile("^cos|sin|tan|ln|log|√|arcos|arcsin|arctan$")

    internal val BINARY = 0
    internal val UNARY = 1

    val OPERATORS: MutableMap<String, IntArray> = HashMap()

    init {
        // Map< "token" , []{Precedence, Arity} >
        OPERATORS["cos"] = intArrayOf(0, UNARY)
        OPERATORS["sin"] = intArrayOf(0, UNARY)
        OPERATORS["tan"] = intArrayOf(0, UNARY)
        OPERATORS["arccos"] = intArrayOf(0, UNARY)
        OPERATORS["arcsin"] = intArrayOf(0, UNARY)
        OPERATORS["arctan"] = intArrayOf(0, UNARY)
        OPERATORS["ln"] = intArrayOf(0, UNARY)
        OPERATORS["log"] = intArrayOf(0, UNARY)
        OPERATORS["+"] = intArrayOf(1, BINARY)
        OPERATORS["-"] = intArrayOf(1, BINARY)
        OPERATORS["*"] = intArrayOf(2, BINARY)
        OPERATORS["/"] = intArrayOf(2, BINARY)
        OPERATORS["^"] = intArrayOf(3, BINARY)
        OPERATORS["√"] = intArrayOf(3, UNARY)
        OPERATORS["!"] = intArrayOf(3, UNARY)
        OPERATORS["%"] = intArrayOf(3, UNARY)
        OPERATORS["$"] = intArrayOf(4, UNARY)// unary negation {-a,-(a),(-a)}
        OPERATORS["("] = intArrayOf(5, BINARY)

    }

    @Throws(Exception::class)
    internal fun isBinary(s: String): Int {
        return OPERATORS[s]!![1]
    }

    private fun toPostFix(tokens: Array<String>): Array<String> {
        val post = ArrayList<String>()// Result
        val stack = Stack<String>()
        for (s in tokens) {
            if (isFunction(s)) {// if its a function then just push it, it will process it last
                stack.push(s)
            }
            if (isOperator(s)) {
                if (s == ")") {// if its a closing parenthesis pop the stack until we find and opening
                    // partenthesis
                    while (!stack.isEmpty()) {
                        val a = stack.peek()
                        if (a != "(")
                            post.add(stack.pop())
                        else {
                            stack.pop()
                            if (!stack.isEmpty() && isFunction(stack.peek())) {
                                post.add(stack.pop())
                            }
                            break
                        }
                    }
                } else {
                    if (!stack.contains("("))
                        try {
                            while (!stack.isEmpty() && cmpPrecedence(s, stack.peek()) <= 0) {
                                if (stack.peek() != "(") {
                                    post.add(stack.pop())
                                }
                            }
                        } catch (e: Exception) {
                            e.printStackTrace()
                        }

                    stack.push(s)
                }
            } else if (isOperand(s)) {
                post.add(s)
            }
        }
        while (!stack.isEmpty()) {
            val s = stack.pop()
            if (s != "(")
                post.add(s)
        }
        //val output = arrayOfNulls<String>(post.size)
        return post.toTypedArray()
    }

    fun toPostFix(code: String): Array<String> {
        val tokens = tokenizer(code)
        return toPostFix(expressionCleaner(tokens))
    }

    /**
     * Divides the expression into tokens, for example "5+5" will become
     * ["5","+","5"]
     *
     * @param code A mathematical expression
     * @return a String array with
     */
    fun tokenizer(code: String): Array<String> {
        val result = ArrayList<String>()
        val matcher = tokenPattern.matcher(code)

        while (matcher.find()) {
            result.add(matcher.group(1))
        }
        val output = arrayOfNulls<String>(result.size)
        return result.toTypedArray()
    }

    /**
     * Changes π and e to their values, changes the minus operator to the unary
     * minus($) and add * when 2 parenthesis meet
     */
    private fun expressionCleaner(tokens: Array<String>): Array<String> {
        val result = ArrayList<String>()

        var i = 0
        while (i < tokens.size) {

            when (tokens[i]) {
                "-" // if equals to - and is at the beginning of the expression or before ( change
                ->
                    // it to $
                    if (i == 0 || result[i - 1] == "(")
                        tokens[i] = "$"
                "π" -> tokens[i] = Math.PI.toString()
                "e" -> tokens[i] = Math.E.toString()
            }

            // () () | n funct | n! n | n n | n (). become ()*() | n*funct | n*n | n!*n |
            // n*()
            if (i > 0 && (tokens[i] == "(" || isFunction(tokens[i]) || isOperand(tokens[i]))) {
                if (Parser.isOperand(tokens[i - 1]) || tokens[i - 1] == ")" || tokens[i - 1] == "!") {
                    result.add("*")
                }
            }
            // () n | funct() n . become ()*n | funct()*n
            if (i < tokens.size - 1) {
                if (tokens[i] == ")" && Parser.isOperand(tokens[i + 1])) {
                    result.add(")")
                    result.add("*")
                    i++
                }

            }
            if (tokens[i].contains("E")) {
                val bd = BigDecimal(tokens[i])
                tokens[i] = bd.toPlainString()
            }
            result.add(tokens[i])
            i++
        }

        val output = arrayOfNulls<String>(result.size)
        return result.toTypedArray()

    }

    // Returns if the token matches the corresponding regular expression
    fun isOperand(token: String): Boolean {
        return operanPattern.matcher(token).matches()
    }

    fun isName(token: String): Boolean {
        return namePattern.matcher(token).matches()
    }

    fun isOperator(token: String): Boolean {
        return opPattern.matcher(token).matches()
    }

    fun isFunction(token: String): Boolean {
        return funcPattern.matcher(token).matches()
    }

    /**
     * Compares the precedence of two operators if they exist in the OPERATOR map
     *
     * @return The difference between the two precedences
     */
    @Throws(Exception::class)
    fun cmpPrecedence(t1: String, t2: String): Int {
        return OPERATORS[t1]!![0] - OPERATORS[t2]!![0]
    }

    /**
     * Removes the last ".0" from a number
     */
    fun toInt(number: String): String {

        return if (number.endsWith(".0")) {
            number.substring(0, number.length - 2)
        } else number

    }
}
